﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text.RegularExpressions;
using System.Web.UI.HtmlControls;
using System.Collections.Specialized;
using System.Text;

public partial class WikiControl : System.Web.UI.UserControl
{
    private const string _defaultPage = "Home";
    private const string _parameterName = "page"; //Wiki PageID
    private const string _aspxExtension = ".aspx";

    private bool _usePathInfo = true;
    public bool UsePathInfo
    {
        get
        {
            //If the current page has query string info (the raw un-url rewritten value) we should use the query string string technique and not path info.
            //Note: We can't use Request.PathInfo when URL Rewriting is in place (i.e in BlogEngine.NET)
            //The path info component is lost so we have to manually extract it from the RawURL.
            if (Request.RawUrl.TrimEnd('?').Contains('?'))
            {
                return false;
            }
            else return _usePathInfo;
        }
        set
        {
            _usePathInfo = value;
        }
    }
    
    public bool DisplayVersionHistory { get; set; }
    
    public bool DisableTitleModification { get; set; }

    public bool DisplayQuickLinks { get; set; }

    private const string _defaultSytaxHighlighterBrush = "c#";
    private string _syntaxHighlighterBrush;
    public string SyntaxHighlighterBrush
    {
        get
        {
            if (String.IsNullOrEmpty(_syntaxHighlighterBrush)) return _defaultSytaxHighlighterBrush;
            else return _syntaxHighlighterBrush; 
        }
        set
        {
            _syntaxHighlighterBrush = value;
        }
    }

    //Multiple instances of the wiki can exist on the same database.
    private string _instanceID = "Default";
    public string InstanceID
    {
        get
        {
            return _instanceID;
        }
        set
        {
            _instanceID = value;
        }
    }

    private string pageID; //cached result;
    protected string PageID
    {
        get
        {
            return BreadCrumbs[BreadCrumbs.Length - 1];
        }
    }

    private string LinkToPageID(string pageTitle)
    {
        return "<a class='wikiLink' href='" + UrlToPageID(pageTitle) + "'>" + pageTitle + "</a>";
    }

    private string UrlToPageID(string pageID)
    {
        pageID = Hyphenate(pageID);
        //Important that we use RawUrl since this is the original request before Url Rewriting has modified it.
        string rawUrl = Request.RawUrl;
        rawUrl = rawUrl.TrimEnd('?'); //trim off unused ? at the end of the RawUrl
        
        if (UsePathInfo)
        {
            //If there is already some PathInfo clear it with the new page ID (assuming the current site isn't using PathInfo already!)
            //This may need to be rewritten if that case needs to be supported.
            if (pageID == _defaultPage)
                return rawUrl.Substring(0, rawUrl.IndexOf(_aspxExtension) + _aspxExtension.Length); 
            if (rawUrl.Contains(pageID))
                return rawUrl.Substring(0, rawUrl.IndexOf(pageID) + pageID.Length); 
            else
                return rawUrl + "/" + pageID;
        }
        //Need to maintain the current query string values otherwise the right page may not even display.
        else
        {
            string pattern = _parameterName + "=([^&]*)";
            string replace = null;
            string match = Regex.Match(rawUrl, pattern).ToString(); 

            if (pageID == _defaultPage)
                replace = ""; 
            else if (match.Contains(pageID))
                replace = match.Substring(0, match.IndexOf(pageID) + pageID.Length);
            else
                replace = match + "/" + Server.UrlEncode(pageID);

            //Preserve original query string.
            if (rawUrl.Contains(_parameterName + "="))
            {
                return Regex.Replace(rawUrl, pattern, replace);
            }
            else
            {
                string param = _parameterName + "=" + Server.UrlEncode(pageID); 
                if (rawUrl.Contains("?"))
                    return rawUrl + "&" + param;
                else
                    return rawUrl + "?" + param;
            }
        }
    }



    protected int GetLatestPageVersion()
    {
        using (WikiDataContext ctx = new WikiDataContext())
        {
            var query = from p in ctx.WikiEntries
                        where
                            p.InstanceID == InstanceID &&
                            p.PageID == PageID
                        select p;

            if (query.Count() > 0)
            {
                return (int)query.Max(p => p.Version);
            }
            else
            {
                return 0;
            }
        }
    }

    protected string[] BreadCrumbs
    {
        get
        {
            List<string> breadCrumbs = new List<string>();
            breadCrumbs.Add(_defaultPage);
            string path; 
            if (UsePathInfo)
            {
                path = Request.RawUrl.Substring(Request.RawUrl.IndexOf(_aspxExtension) + _aspxExtension.Length).TrimEnd('/').TrimEnd('?').TrimStart('/');
            }
            else
            {
                path = Request.QueryString[_parameterName];   
            }

            if (!String.IsNullOrEmpty(path))
                breadCrumbs.AddRange(path.Split('/').Select(breadCrumb => UnHyphenate(breadCrumb)));

            return breadCrumbs.ToArray();
            
        }
    }

    protected int PageVersion
    {
        get
        {
            if (ViewState["PageVersion"] == null)
            {
                ViewState["PageVersion"] = GetLatestPageVersion();
            }
            return (int)ViewState["PageVersion"];
        }
        set
        {
            ViewState["PageVersion"] = value;
        }
    }

    protected void ActiveViewChanged(object sender, EventArgs args)
    {
        if (MultiView1.Views[MultiView1.ActiveViewIndex].ID == "editView")
        {
            HtmlLink wmdCss = new HtmlLink();
            wmdCss.Attributes["rel"] = "stylesheet";
            wmdCss.Attributes["type"] = "text/css";
            wmdCss.Href = ResolveUrl("Markdown/wmd.css");
            Page.Header.Controls.Add(wmdCss);

            HtmlGenericControl showdownRef = new HtmlGenericControl();
            showdownRef.TagName = "script";
            showdownRef.Attributes["src"] = ResolveUrl("Markdown/showdown.js");
            showdownRef.Attributes["type"] = "text/javascript";
            Page.Header.Controls.Add(showdownRef);


            ScriptManager.RegisterClientScriptBlock(this, this.GetType(), "SyntaxHighlight", @"
                function wikiControl_syntaxHighlight() {
                    wikiControl_replaceCodeTags();
                    SyntaxHighlighter.highlight();
                }", true);

            ScriptManager.RegisterStartupScript(this, this.GetType(), "CheckSyntaxHighlighter", @"
                if (typeof(SyntaxHighlighter) == 'undefined' || typeof(jQuery) == 'undefined') 
                    document.getElementById('wikiControl_formatCodeButton').style.display = 'none';
            ", true);
        }
    }
    bool _hasOtherPages;
    protected void Page_Load(object sender, EventArgs e)
    {
        //We need to define the image references here using ResolveUrl since the reference in the aspx does not seem to resolve properly when 
        //using PathInfo.
        ImageFirst.ImageUrl = ResolveUrl("Images/first.png");
        ImagePrevious.ImageUrl = ResolveUrl("Images/previous.png");
        ImageNext.ImageUrl = ResolveUrl("Images/next.png");
        ImageLast.ImageUrl = ResolveUrl("Images/last.png");
        ImageEdit.ImageUrl = ResolveUrl("Images/edit.png");
        ImageSave.ImageUrl = ResolveUrl("Images/tick.png");
        ImageCancel.ImageUrl = ResolveUrl("Images/cross.png");

        string notDirty = "dirty = false;";
        string dirty = "dirty = true;";

        SaveButton.Attributes["onclick"] = notDirty;
        CancelButton.Attributes["onclick"] = notDirty;
        WikiTitleTextBox.Attributes["onkeyup"] = dirty;
        wmd_input.Attributes["onkeyup"] = dirty; 

        ScriptManager.RegisterClientScriptBlock(this, this.GetType(), "ReplaceCodeTags", @"
            function wikiControl_replaceCodeTags() {
                $('pre:has(code)').addClass('brush: "+SyntaxHighlighterBrush+@"');
                $('pre>code').each(function(index) {
                    var cnt = $(this).contents();
                    $(this).replaceWith(cnt);    
                });
            }", true);


        ScriptManager.RegisterClientScriptBlock(this, this.GetType(), "ProcessCodeTags", @"
            if (typeof(SyntaxHighlighter) != 'undefined' && typeof(jQuery) != 'undefined') {
                $(document).ready(function () { 
                    wikiControl_replaceCodeTags();
                });
             }", true);

        HtmlLink wikiCss = new HtmlLink();
        wikiCss.Attributes["rel"] = "stylesheet";
        wikiCss.Attributes["type"] = "text/css";
        wikiCss.Href = ResolveUrl("Wiki.css");
        Page.Header.Controls.Add(wikiCss);

        if (!DisableTitleModification && PageID != _defaultPage)
        {
            string title = Page.Header.Title;
            if (title != "") title = Page.Header.Title + " | ";
            Page.Header.Title = title + PageID;
        }


        EditButton.Visible = Page.User.Identity.IsAuthenticated;
        toolbarDiv.Visible = Page.User.Identity.IsAuthenticated || DisplayVersionHistory;

        ErrorLabel.Text = "";
        ErrorLabel.Visible = false;
        _hasOtherPages = SetQuickLinks();
        //Don't display quick links if there is only one page, or if the DisplayQuickLinks setting is false. 
        //If the user is logged in, always show.
        if ((!_hasOtherPages || !DisplayQuickLinks) && !Page.User.Identity.IsAuthenticated)
        {
            QuickLinksLabel.Visible = false;
        }

        if (_hasOtherPages)
            SetBreadCrumb();

        if (!IsPostBack)
        {
            LoadPage();
        }
    }

    private void SetBreadCrumb()
    {
        BreadCrumbLabel.InnerHtml = "";
        foreach (string breadCrumb in BreadCrumbs)
        {
            BreadCrumbLabel.InnerHtml = BreadCrumbLabel.InnerHtml + " > " + LinkToPageID(breadCrumb);
        }
    }



    //Returns true if there are other pages linked to in this wiki.
    private bool SetQuickLinks()
    {

        QuickLinksLabel.InnerHtml = "";
        using (WikiDataContext ctx = new WikiDataContext())
        {
            var query = from p in ctx.WikiEntries
                        where
                            p.InstanceID == InstanceID &&
                            p.PageID != _defaultPage
                        select p;

            if (query.Count() == 0)
            {
                return false;
            }
            else
            {
                QuickLinksLabel.Visible = true;
                foreach (string p in query.Select(p => p.PageID).Distinct())
                {
                    QuickLinksLabel.InnerHtml += LinkToPageID(p) + ", ";
                }
                QuickLinksLabel.InnerHtml = QuickLinksLabel.InnerHtml.TrimEnd(',', ' ');
                return true;
            }
        }
    }

    private void LoadPage()
    {
        if (PageVersion > 0)
        {
            using (WikiDataContext ctx = new WikiDataContext())
            {
                try
                {
                    var query = from
                                w in ctx.WikiEntries
                                where
                                    w.InstanceID == InstanceID &&
                                    w.PageID == PageID &&
                                    w.Version == PageVersion
                                select w;

                    WikiEntry entry = query.First();

                    WikiTitleTextBox.Visible = (PageID != _defaultPage);
                    WikiTitleTextBox.Text = entry.PageID;

                    MarkdownSharp.Markdown markdown = new MarkdownSharp.Markdown();
                    markdown.AutoHyperlink = true;

                    string toRender = Regex.Replace(entry.Text, @"\[\[([A-Za-z ,\._\-]*)\]\]", new MatchEvaluator(ReplacePage));
                    WikiEntryLabel.InnerHtml = markdown.Transform(toRender);
                    wmd_input.Text = entry.Text;
                    AuthorLabel.InnerHtml = "By " + entry.AuthorName + "<br />" + entry.TimeCreated.ToShortDateString() + " (revision " + entry.Version + ")";
                }
                catch (Exception ex)
                {
                    WikiEntryLabel.InnerHtml = ex.Message + ex.StackTrace;
                }

                MultiView1.SetActiveView(displayView);
            }
        }
        else
        {
            BackButton.Enabled = false;
            wmd_input.Text = "";
            WikiTitleTextBox.Visible = (PageID != _defaultPage);
            WikiTitleTextBox.Text = PageID;
            // If there is no content, default to Edit Mode.
            if (Page.User.Identity.IsAuthenticated) MultiView1.SetActiveView(editView);
            ErrorLabel.Text = "There is no content for this item, please add some.";
            ErrorLabel.Visible = true;
        }
    }

    public string ReplacePage(Match m)
    {
        return LinkToPageID(m.Groups[1].ToString());
    }

    protected void SaveButton_Click(object sender, EventArgs e)
    {
        if (PageVersion != GetLatestPageVersion())
        {
            ErrorLabel.Text = "Someone else has modified this page since loading. Cannot save entry.";
            ErrorLabel.Visible = true;
        }
        else
        {
            using (WikiDataContext ctx = new WikiDataContext())
            {
                //If title has changed
                if (String.Compare(WikiTitleTextBox.Text, PageID, true) != 0)
                {
                    var query = from w in ctx.WikiEntries
                                where
                                    w.InstanceID == InstanceID &&
                                    w.PageID == WikiTitleTextBox.Text
                                select w;

                    if (query.Count() > 0)
                    {
                        ErrorLabel.Text = "A page of this name already exists, please choose another.";
                        ErrorLabel.Visible = true;
                        return;
                    }

                    //Update the names of the title for all previous entries.
                    query = from w in ctx.WikiEntries
                            where
                                w.InstanceID == InstanceID &&
                                w.PageID == PageID
                            select w;

                    foreach (WikiEntry entry in query.ToList<WikiEntry>())
                    {
                        entry.PageID = WikiTitleTextBox.Text;
                    }
                }

                //Insert the new version of the page.
                ctx.WikiEntries.InsertOnSubmit(new WikiEntry
                {
                    InstanceID = InstanceID,
                    PageID = WikiTitleTextBox.Text,
                    Version = ++PageVersion,
                    AuthorName = Page.User.Identity.Name,
                    Text = wmd_input.Text,
                    TimeCreated = DateTime.Now
                });

                ctx.SubmitChanges();
            }

            Response.Redirect(Request.RawUrl.Replace(Hyphenate(PageID), Hyphenate(WikiTitleTextBox.Text))); 
        }
    }

    private string Hyphenate(string s)
    {
        return s.Replace(" ", "-");
    }

    private string UnHyphenate(string s)
    {
        return s.Replace("-", " ");
    }
    
    protected void CancelButton_Click(object sender, EventArgs e)
    {
        LoadPage();
        MultiView1.SetActiveView(displayView);
    }
    protected void EditButton_Click(object sender, EventArgs e)
    {
        PageVersion = GetLatestPageVersion();
        LoadPage();
        MultiView1.SetActiveView(editView);
    }

    protected void FirstButton_Click(object sender, EventArgs e)
    {
        PageVersion = 1;
        LoadPage();
    }

    protected void LastButton_Click(object sender, EventArgs e)
    {
        PageVersion = GetLatestPageVersion();
        LoadPage();
    }
    protected void BackButton_Click(object sender, EventArgs e)
    {
        if (PageVersion > 1) PageVersion--;
        LoadPage();
    }
    protected void NextButton_Click(object sender, EventArgs e)
    {
        if (PageVersion != GetLatestPageVersion()) PageVersion++;
        LoadPage();
    }
}

